<html>
 <head>
  <meta charset="UTF-8">
 </head>
 <body>
  <h1 data-lake-id="OOA5o" id="OOA5o"><span data-lake-id="u001a8463" id="u001a8463">典型回答</span></h1>
  <p data-lake-id="udf90f2e7" id="udf90f2e7"><br></p>
  <p data-lake-id="u2f3dddfb" id="u2f3dddfb"><span data-lake-id="ub4bd8d0e" id="ub4bd8d0e">Java中类的加载阶段分为加载、链接、初始化。其中连接过程又包含了验证、准备和解析。</span></p>
  <p data-lake-id="uf502eba5" id="uf502eba5"><br></p>
  <h3 data-lake-id="YDFdj" id="YDFdj"><span data-lake-id="u73973526" id="u73973526">加载阶段</span></h3>
  <p data-lake-id="u5f27c71a" id="u5f27c71a"><br></p>
  <p data-lake-id="u94118648" id="u94118648"><span data-lake-id="u1bbe1578" id="u1bbe1578">查找并加载类的二进制数据</span><em><span data-lake-id="uc9c4c203" id="uc9c4c203">(网络，jar包，运行时生成等)</span></em><span data-lake-id="u6ac81222" id="u6ac81222">。</span><strong><span data-lake-id="u445d0202" id="u445d0202">将类的.class文件中的二进制数据读入内存中，将其放在方法区中，然后创建一个java.lang.Class对象（存放在堆中）用来封装类在方法区的数据结构</span></strong></p>
  <p data-lake-id="u0f5befff" id="u0f5befff"><strong><span data-lake-id="ud00a5972" id="ud00a5972">​</span></strong><br></p>
  <h3 data-lake-id="nATvC" id="nATvC"><span data-lake-id="u368e880a" id="u368e880a">链接阶段</span></h3>
  <p data-lake-id="uee453b0b" id="uee453b0b"><br></p>
  <p data-lake-id="ubcf05103" id="ubcf05103"><span data-lake-id="u3c93e179" id="u3c93e179">在链接阶段，Java类加载器对类进行验证、准备和解析操作。将类与类的关系（符号引用转为直接引用）确定好，校验字节码</span></p>
  <p data-lake-id="u2e1db801" id="u2e1db801"><span data-lake-id="u09a4dc0c" id="u09a4dc0c">​</span><br></p>
  <ol list="u05c63ca5">
   <li fid="ud68de7dd" data-lake-id="u1c0577fb" id="u1c0577fb"><span data-lake-id="uec2461fe" id="uec2461fe">验证：校验类的正确性（文件格式，元数据，字节码，二进制兼容性）</span></li>
   <li fid="ud68de7dd" data-lake-id="u756bad2b" id="u756bad2b"><span data-lake-id="u0df92775" id="u0df92775">准备：为类的</span><strong><span data-lake-id="ub3afb2ce" id="ub3afb2ce">静态变量分配内存</span></strong><span data-lake-id="u1a6de2d3" id="u1a6de2d3">，将其初始化为默认值。但是在到达初始化之前，类变量都没有初始化为真正的初始值。</span><strong><span data-lake-id="ub91149e3" id="ub91149e3">当然，对于final修饰的变量来说，会在此时就被初始化为指定值</span></strong></li>
   <li fid="ud68de7dd" data-lake-id="u1dc5aaf7" id="u1dc5aaf7"><span data-lake-id="u899f2b71" id="u899f2b71">解析：把类的符号引用转为直接引用</span><em><span data-lake-id="u93948411" id="u93948411">(类或接口、字段、类方法、接口方法、方法类型、方法句柄和访问控制修饰符7类符号引用 )</span></em></li>
  </ol>
  <p data-lake-id="u47deb60a" id="u47deb60a"><br></p>
  <h3 data-lake-id="z6CV1" id="z6CV1"><span data-lake-id="u42b30be3" id="u42b30be3">初始化阶段</span></h3>
  <p data-lake-id="u0b1cca5a" id="u0b1cca5a"><br></p>
  <p data-lake-id="u7c02bbd1" id="u7c02bbd1"><span data-lake-id="u35f1dbe9" id="u35f1dbe9">初始化是类加载的最后一步，也是真正执行类中定义的 Java 程序代码(字节码)，初始化阶段是执行类构造器 </span><code data-lake-id="u66beff33" id="u66beff33"><span data-lake-id="u0184877c" id="u0184877c">&lt;clinit&gt; ()</span></code><span data-lake-id="uad18f842" id="uad18f842">方法的过程。这</span><strong><span data-lake-id="uff79edea" id="uff79edea">里利用了一种懒加载的思想，所有Java虚拟机实现必须在每个类或接口被Java程序首次主动使用才初始化</span></strong><span data-lake-id="ubfda61ea" id="ubfda61ea">，但类加载不一定，静态代码块在类</span><strong><span data-lake-id="u77116578" id="u77116578">初始化</span></strong><span data-lake-id="u91e7fb22" id="u91e7fb22">时执行</span></p>
  <p data-lake-id="u82f96921" id="u82f96921"><span data-lake-id="udccb30e7" id="udccb30e7">​</span><br></p>
  <ol list="u27ff8ebb">
   <li fid="u17e87899" data-lake-id="ud1df4fb6" id="ud1df4fb6"><span data-lake-id="u10d7333e" id="u10d7333e">当遇到 new 、 getstatic、putstatic或invokestatic 这4条字节码指令时，比如 new 一个类，读取一个静态字段(未被 final 修饰)、或调用一个类的静态方法时</span></li>
   <li fid="u17e87899" data-lake-id="u3c09da5d" id="u3c09da5d"><span data-lake-id="u1a36198c" id="u1a36198c">使用 </span><code data-lake-id="u6f7f6e16" id="u6f7f6e16"><span data-lake-id="udebd6dd1" id="udebd6dd1">java.lang.reflect</span></code><span data-lake-id="ub0e5e7ed" id="ub0e5e7ed"> 包的方法对类进行反射调用时 ，如果类没初始化，需要触发其初始化</span></li>
   <li fid="u17e87899" data-lake-id="uc4e72020" id="uc4e72020"><span data-lake-id="u6e791a07" id="u6e791a07">初始化一个类，如果其父类还未初始化，则先触发该父类的初始化</span></li>
   <li fid="u17e87899" data-lake-id="u9ea5deec" id="u9ea5deec"><span data-lake-id="u0163940b" id="u0163940b">当虚拟机启动时，用户需要定义一个要执行的主类 (包含 main 方法的那个类)，虚拟机会先初始化这个类</span></li>
   <li fid="u17e87899" data-lake-id="u4dc7fe7b" id="u4dc7fe7b"><span data-lake-id="u521991cf" id="u521991cf">当使用 JDK1.7 的动态动态语言时，如果一个 MethodHandle 实例的最后解析结构为 REF_getStatic、REF_putStatic、REF_invokeStatic、的方法句柄，并且这个句柄没有初始化，则需要先触发器初始化</span></li>
  </ol>
  <p data-lake-id="u455f38a7" id="u455f38a7"><span data-lake-id="ub4334136" id="ub4334136">​</span><br></p>
  <p data-lake-id="ud96ef8ed" id="ud96ef8ed"><span data-lake-id="u33792f90" id="u33792f90">​</span><br></p>
  <h1 data-lake-id="CXjRO" id="CXjRO"><span data-lake-id="uacc078eb" id="uacc078eb">扩展知识</span></h1>
  <p data-lake-id="u5d59832b" id="u5d59832b"><br></p>
  <h2 data-lake-id="QGtpm" id="QGtpm"><span data-lake-id="u4edd2798" id="u4edd2798">什么是符号引用和直接引用</span></h2>
  <p data-lake-id="uae454d48" id="uae454d48"><br></p>
  <p data-lake-id="u65c53920" id="u65c53920"><strong><span data-lake-id="uee923ae2" id="uee923ae2">符号引用（Symbolic Reference）</span></strong><span data-lake-id="ueea53518" id="ueea53518">是一种用来表示引用目标的符号名称，比如类名、字段名、方法名等。符号引用与实际的内存地址无关，只是一个标识符，用于描述被引用的目标，类似于变量名。符号引用是在编译期间产生的，在编译后的class文件中存储。</span></p>
  <p data-lake-id="ue7b235d6" id="ue7b235d6"><span data-lake-id="u62b74cb7" id="u62b74cb7">​</span><br></p>
  <p data-lake-id="ueffe8604" id="ueffe8604"><strong><span data-lake-id="u28bd71bb" id="u28bd71bb">直接引用（Direct Reference）</span></strong><span data-lake-id="u1c048ff2" id="u1c048ff2">是实际指向目标的内存地址，比如类的实例、方法的字节码等。直接引用与具体的内存地址相关，是在程序运行期间动态生成的。</span></p>
  <p data-lake-id="uc02c4f09" id="uc02c4f09"><br></p>
  <p data-lake-id="uaa4b0372" id="uaa4b0372"><span data-lake-id="u1e94c77a" id="u1e94c77a">假设有两个类A和B，其中A类中有一个成员变量x，B类中有一个方法foo，其中会调用A类中的成员变量x：</span></p>
  <p data-lake-id="u00e27d71" id="u00e27d71"><span data-lake-id="u8f9e4a6c" id="u8f9e4a6c">​</span><br></p>
  <pre lang="java"><code>
public class A {
    public int x;
}

public class B {
    public void foo() {
        A a = new A();
        a.x = 10;
        System.out.println("x = " + a.x);
    }
}

</code></pre>
  <p data-lake-id="uddb3dbb1" id="uddb3dbb1"><br></p>
  <p data-lake-id="u3cbbcc52" id="u3cbbcc52"><span data-lake-id="ud3a04b13" id="ud3a04b13">在B类中调用A类的成员变量x时，实际上是通过符号引用来引用A类中的x变量。在解析阶段，Java虚拟机会将A类中的符号引用转换为直接引用，定位到具体的x变量实现，并为B类生成一条指令，用于获取该变量的内存地址。</span></p>
  <p data-lake-id="u6ac1775b" id="u6ac1775b"><span data-lake-id="uc87282a7" id="uc87282a7">​</span><br></p>
  <p data-lake-id="uf84f1ba2" id="uf84f1ba2"><span data-lake-id="uf0234d18" id="uf0234d18">假设A类的x变量的内存地址为0x1000，在解析阶段，Java虚拟机会为B类生成一条指令，用于获取x变量的内存地址，比如：</span></p>
  <p data-lake-id="u7fae8a12" id="u7fae8a12"><span data-lake-id="u08f4d959" id="u08f4d959">​</span><br></p>
  <pre lang="java"><code>
getstatic 0x1000
</code></pre>
  <p data-lake-id="u09e1d49b" id="u09e1d49b"><span data-lake-id="u034dda01" id="u034dda01">​</span><br></p>
  <p data-lake-id="ub38fbd4e" id="ub38fbd4e"><span data-lake-id="uce13742a" id="uce13742a">这条指令会将0x1000作为直接引用，用于访问A类中的x变量。</span></p>
  <p data-lake-id="u8efcde03" id="u8efcde03"><span data-lake-id="ueb3b7ed7" id="ueb3b7ed7">​</span><br></p>
  <p data-lake-id="u0321315a" id="u0321315a"><strong><span data-lake-id="ub636fc85" id="ub636fc85">也就是说，在类的解析阶段进行的，Java虚拟机会根据符号引用定位到具体的内存地址，并生成一条指令，用于访问该内存地址。</span></strong></p>
 </body>
</html>